package de.g18.scavengerhunt.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.provider.Settings.Secure;
import android.util.Log;

import com.google.android.maps.GeoPoint;

import de.g18.scavengerhunt.AppApplicationContext;
import de.g18.scavengerhunt.AppConstants;
import de.g18.scavengerhunt.types.BattleZone;
import de.g18.scavengerhunt.types.DatabaseAdapter;
import de.g18.scavengerhunt.types.HttpTask;
import de.g18.scavengerhunt.types.Session;
import de.g18.scavengerhunt.types.User;

/**
 * @author Ich
 */
public class WebserviceUtil {

   private static final String   LOG_TAG                = WebserviceUtil.class.getSimpleName();

    private static WebserviceUtil instance               = null;

    private static Context        context                = null;

    private static boolean       lastRequestSuccessfull = false;

    private WebserviceUtil() {
    }

    public static WebserviceUtil getInstance() {
        synchronized (instance) {
            if (instance == null) {
                instance = new WebserviceUtil();
            }
        }
        return instance;
    }

    public User getUserById(int id) {
        try {
            String jsonResponse = null;
            String url = AppConstants.USER_PATH + id + "/";
            String method = String.valueOf(HttpUtil.MethodType.GET);
            jsonResponse = new HttpTask().execute(url, method).get();
            return jsonToUser(jsonResponse);
        } catch (Exception e) {
            return null;
        }
    }

    public User createUser(String name, String deviceId) {
        try {
            String jsonResponse = null;
            String url = AppConstants.USER_PATH;
            String method = String.valueOf(HttpUtil.MethodType.POST);

            Map<String, Object> paramMap = new HashMap<String, Object>();
            paramMap.put("name", String.valueOf(name));
            paramMap.put("deviceId", deviceId);
            jsonResponse = new HttpTask().execute(url, method, paramMap).get();
            return jsonToUser(jsonResponse);
        } catch (Exception e) {
            return null;
        }
    }

    public boolean setUserName(int userId, String userName) {
        String url = AppConstants.USER_PATH + userId + "/";
        String method = String.valueOf(HttpUtil.MethodType.POST);

        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("name", userName);

        new HttpTask().execute(url, method, paramMap);
        return true;
    }

    /**
     * Returns all active sessions from the webservice
     * 
     * @return a list of sessions
     */
    public List<Session> getActiveSessions() {
        try {
            String jsonResponse = null;
            String url = AppConstants.SESSION_PATH;
            String method = String.valueOf(HttpUtil.MethodType.GET);
            List<Session> sessions = new ArrayList<Session>();

            jsonResponse = new HttpTask().execute(url, method).get();

            if (jsonResponse != null) {
                // parse the JSON string to objects
                JSONArray array = new JSONArray(jsonResponse);
                for (int i = 0; i < array.length(); i++) {
                    Session session = jsonToSession(array.getJSONObject(i));
                    if (session != null) {
                        sessions.add(session);
                    }
                }
            } else {
                Log.w("WebserviceUtil", "jsonResponse ist NULL !");
                return new ArrayList<Session>();
            }
            return sessions;
        } catch (Exception e) {
            return new ArrayList<Session>();
        }
    }

    public List<User> getActiveUsers(int sessionId) {
        try {
            String jsonResponse = null;
            String url = AppConstants.USER_PATH + "position/?session=" + sessionId;
            String method = String.valueOf(HttpUtil.MethodType.GET);
            List<User> sessionUser = new ArrayList<User>();

            HttpTask httpTask = new HttpTask();
            jsonResponse = httpTask.execute(url, method).get();
            if (!httpTask.isSuccess()) {
                NotificationUtil.showNoNetworkConnectionNotification();
                return new ArrayList<User>();
            }

            if (jsonResponse != null && !jsonResponse.equals("")) {
                JSONArray array = new JSONArray(jsonResponse);
                for (int i = 0; i < array.length(); i++) {
                    JSONObject jsonUser = array.getJSONObject(i);

                    int userId = (Integer) jsonUser.get("id");
                    String userName = (String) jsonUser.get("name");
                    String userDeviceId = (String) jsonUser.get("deviceId");
                    try {
                        JSONObject userPosition = jsonUser.getJSONObject("position");

                        int lat = (Integer) userPosition.get("lat");
                        int ltg = (Integer) userPosition.get("lng");

                        GeoPoint userGeoPoint = new GeoPoint(lat, ltg);

                        User user = new User(userId, userDeviceId, userName, userGeoPoint);

                        sessionUser.add(user);
                    } catch (JSONException e) {
                        Log.w("WebserviceUtil", "User with id " + userId + " was ignored, caused by wrong position data.");
                    }
                }
            }
            return sessionUser;
        } catch (Exception e) {
            NotificationUtil.showNoNetworkConnectionNotification();
            return new ArrayList<User>();
        }
    }

    /**
     * Returns all user (with positions included) of a session
     * 
     * @param sessionId the id of the current sessions
     * @return a list of user
     */
    public List<User> getUserPositions(int sessionId) {
        try {
            String jsonResponse = null;
            String url = AppConstants.USER_PATH + "position/?session=" + sessionId;
            String method = String.valueOf(HttpUtil.MethodType.GET);
            List<User> sessionUser = new ArrayList<User>();

            HttpTask httpTask = new HttpTask();
            jsonResponse = httpTask.execute(url, method).get();
            // alert only once, after connection is lost,
            // next alert only after reconnect and next connection loss
            if (!httpTask.isSuccess() && lastRequestSuccessfull) {
                NotificationUtil.showNoNetworkConnectionNotification();
                lastRequestSuccessfull = false;
                return new ArrayList<User>();
            }

            if (jsonResponse != null && !jsonResponse.equals("")) {
                JSONArray array = new JSONArray(jsonResponse);
                for (int i = 0; i < array.length(); i++) {
                    // build user from JSON string
                    JSONObject jsonUser = array.getJSONObject(i);

                    int userId = (Integer) jsonUser.get("id");
                    String userName = (String) jsonUser.get("name");
                    String userDeviceId = (String) jsonUser.get("deviceId");
                    try {
                        JSONObject userPosition = jsonUser.getJSONObject("position");

                        int lat = (Integer) userPosition.get("lat");
                        int ltg = (Integer) userPosition.get("lng");

                        GeoPoint userGeoPoint = new GeoPoint(lat, ltg);

                        User user = new User(userId, userDeviceId, userName, userGeoPoint);

                        sessionUser.add(user);
                    } catch (JSONException e) {
                        Log.w("WebserviceUtil", "User with id " + userId + " was ignored, caused by wrong position data.");
                    }
                }
            } else {
                return sessionUser;
            }
            // store success, to trigger notifications only once
            lastRequestSuccessfull = true;
            return sessionUser;
        } catch (Exception e) {
            if (lastRequestSuccessfull) {
                NotificationUtil.showNoNetworkConnectionNotification();
                lastRequestSuccessfull = false;
            }
            return new ArrayList<User>();
        }
    }

    /**
     * Adds the given user to a session
     * 
     * @param user the user to add
     * @param session the session, to add the user
     */
    public boolean addUserToSession(User user, Session session) {
        String url = AppConstants.SESSION_PATH + String.valueOf(session.getId()) + "/user/" + String.valueOf(user.getId()) + "/";
        String method = String.valueOf(HttpUtil.MethodType.POST);
        HttpTask httpTask = new HttpTask();
        httpTask.execute(url, method);
        return true;
    }

    private User jsonToUser(String jsonResponse) {
        JSONObject json;
        try {
            json = new JSONObject(jsonResponse);

            int userId = (Integer) json.get("id");
            String userName = (String) json.get("name");
            String userDeviceId = (String) json.get("deviceId");

            return new User(userId, userDeviceId, userName);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Creates a session from JSON
     * 
     * @param json source JSON string
     * @return the session created
     */
    private Session jsonToSession(JSONObject json) {
        Session session = null;
        BattleZone battlezone = null;
        try {
            int sessionId = (Integer) json.get("id");
            String sessionName = (String) json.get("name");
            int mrXUserId = 0;
            int timestampSessionCreated = 0;
            try {
                mrXUserId = Integer.valueOf((String) json.getString("mrX"));
                timestampSessionCreated = Integer.valueOf((String) json.getString("start"));
                JSONObject jsonBattlezone = (JSONObject) json.get("battlezone");
                String bzName = jsonBattlezone.getString("name");
                int bzLat = Integer.valueOf((String) jsonBattlezone.getString("lat"));
                int bzLng = Integer.valueOf((String) jsonBattlezone.getString("lng"));
                int bzRadius = Integer.valueOf((String) jsonBattlezone.getString("radius"));

                GeoPoint bzCenterPosition = new GeoPoint(bzLat, bzLng);
                battlezone = new BattleZone(bzCenterPosition, bzRadius);
                battlezone.setName(bzName);
            } catch (ClassCastException e) {
                Log.e(LOG_TAG, "ClassCastException in jsonToSession with JSONObject " + json.toString());
            }

            session = new Session(sessionId, sessionName, mrXUserId, battlezone, timestampSessionCreated);
        } catch (Exception e) {
            // TODO Error handling
        }

        return session;
    }

    /**
     * Create a session from given parameters
     * 
     * @param sessionName
     * @param mrXUserId
     * @param battleZone
     */
    public boolean createSession(String sessionName, int mrXUserId, BattleZone battleZone) {
        try {
            String jsonResponse = null;
            String url = AppConstants.SESSION_PATH;
            String method = String.valueOf(HttpUtil.MethodType.POST);

            Map<String, Object> paramMap = new HashMap<String, Object>();
            paramMap.put("name", sessionName);
            paramMap.put("mrX", String.valueOf(mrXUserId));
            paramMap.put("battlezone_name", String.valueOf(battleZone.getName()));
            paramMap.put("battlezone_lng", String.valueOf(battleZone.getCircleCenter().getLongitudeE6()));
            paramMap.put("battlezone_lat", String.valueOf(battleZone.getCircleCenter().getLatitudeE6()));
            paramMap.put("battlezone_radius", String.valueOf(battleZone.getRadiusInMeters()));

            jsonResponse = new HttpTask().execute(url, method, paramMap).get();
            Session session = jsonToSession(new JSONObject(jsonResponse));
            if (session != null) {
                AppApplicationContext.setSession(session);
                return true;
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Send the device's position to the web service
     * 
     * @param userId the own user id
     * @param latitude position lat
     * @param longitude position lng
     */
    public void sendOwnPosition(int userId, int latitude, int longitude) {
        String url = AppConstants.USER_PATH + userId + "/position/";
        String method = String.valueOf(HttpUtil.MethodType.POST);

        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("lat", String.valueOf(latitude));
        paramMap.put("lng", String.valueOf(longitude));

        Log.d("WebserviceUtil", "Updating position: Lat = " + String.valueOf(latitude) + ", Ltd = " + String.valueOf(longitude));
        HttpTask httpTask = new HttpTask();
        httpTask.execute(url, method, paramMap);
        if (!httpTask.isSuccess() && lastRequestSuccessfull) { // trigger notification only once...
            NotificationUtil.showNoNetworkConnectionNotification();
            lastRequestSuccessfull = false;
        }
        lastRequestSuccessfull = httpTask.isSuccess();
    }

    /**
     * Removes a user from a session.
     * 
     * @param userId
     * @param sessionId
     * @return boolean
     */
    public boolean leaveSession(int userId, int sessionId) {
        boolean retValue = false;
        try {
            String url = AppConstants.SESSION_PATH + sessionId + "/user/" + userId + "/";
            String method = String.valueOf(HttpUtil.MethodType.DELETE);
            new HttpTask().execute(url, method);
            retValue = true;
        } catch (Exception e) {
            NotificationUtil.showNoNetworkConnectionNotification();
            Log.e(LOG_TAG, "Error while leaveSession: " + e.getMessage());
        }
        return retValue;
    }

    /**
     * Registers the current user on the webservice if not registered.
     */
    public void handleUserRegistration() {
        DatabaseAdapter databaseAdapter = new DatabaseAdapter(context);
        User user = null;
        int userId = databaseAdapter.getUserId();
        String userName = databaseAdapter.getUserName();
        // Check if the username is set correctly.
        if (!isUserIdSet() || !verifyUserId(userId)) {
            user = createUser(userName, Secure.getString(context.getContentResolver(), Secure.ANDROID_ID));
        } else {
            user = getUserById(userId);
        }
        setCurrentUser(user);
    }

    private boolean isUserIdSet() {
        DatabaseAdapter databaseAdapter = new DatabaseAdapter(context);
        int userid = databaseAdapter.getUserId();
        if (userid > 0) {
            return true;
        }
        return false;
    }

    private boolean verifyUserId(int userId) {
        if (getUserById(userId) == null) {
            return false;
        } else {
            return true;
        }
    }

    private boolean setCurrentUser(User user) {
        DatabaseAdapter databaseAdapter = new DatabaseAdapter(context);
        if (user != null) {
            databaseAdapter.setUserId(user.getId());
            databaseAdapter.setUserName(user.getName());
            AppApplicationContext.setUser(user);
            return true;
        }
        return false;
    }

    public void setContext(Context context) {
        WebserviceUtil.context = context;
    }

    public boolean uploadProfileImage(int userId, String encodedImage) {
        String url = AppConstants.USER_PATH + userId + "/";
        String method = String.valueOf(HttpUtil.MethodType.POST);

        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("image", encodedImage);

        new HttpTask().execute(url, method, paramMap);
        return true;
    }

    public String receiveEncodedProfileImage(int userId) {
        String retValue = null;
        try {
            String url = AppConstants.USER_PATH + userId + "/" + AppConstants.IMAGE_PATH;
            String method = String.valueOf(HttpUtil.MethodType.GET);
            retValue = jsonStringToUnescapedBase64Response(new HttpTask().execute(url, method).get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * Gets a unescaped Base64 string from a escaped JSON string
     * 
     * @param jsonResponse (escaped)
     * @return Base64 encoded, unescaped String
     */
    private String jsonStringToUnescapedBase64Response(String escapedJsonString) {
        String retValue = escapedJsonString.replaceAll("^\\[\"", "");
        retValue = retValue.replaceAll("\\]\"$", "");
        retValue = retValue.replaceAll("\\\\/", "/");
        retValue = retValue.replaceAll("\\\\n", System.getProperty("line.separator"));
        retValue = retValue.replaceAll(" ", "+");
        return retValue;
    }
}
